using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using CloudShot.Logic;
using CloudShot.Utils.Drawings;

namespace CloudShot.Editor.CanvasFigures
{
  public static class ArrowVertexType
  {
    public const int Start = 1;
    public const int Middle = 2;
    public const int End = 3;
  }

  /// <summary>
  ///     Line graphic object
  /// </summary>
  public class CanvasArrow : CanvasObject
  {
    private const int HOT_SPOT_SIZE = 7;
    private const float Tension = 0.7f;

    private Point _endPoint;
    private bool _isMiddlePointSet;
    private Point _middlePoint;
    private Point _startPoint;
    
    public override int HandlesCount
    {
      get { return 3; }
    }

    public bool IsArrowAtStart { get; set; }
    public bool IsArrowAtEnd { get; set; }

    //fot hit test. TODO refactor
    protected GraphicsPath AreaPath { get; set; }
    protected Region AreaRegion { get; set; }
    private Pen AreaPen { get; set; }
    private const int SelectWidthRegion = 20;
    private int _regionWidth = -1;

    public CanvasArrow() : this(0, 0, 1, 1) {}

    public CanvasArrow(int x1, int y1, int x2, int y2)
    {
      AreaRegion = null;
      AreaPen = null;
      AreaPath = null;

      _startPoint.X = x1;
      _startPoint.Y = y1;
      _endPoint.X = x2;
      _endPoint.Y = y2;
      _middlePoint = MidPoint(_startPoint, _endPoint);

      IsArrowAtEnd = true;
    }

    public override Rectangle GetBoundingBox()
    {
      using (var graphicsPath = CreateArrowPath())
      {
        var rectF = graphicsPath.GetBounds();

        if (IsSelected)
        {
          int halfTrackerSize = TrackerSize / 2;
          rectF.Inflate(halfTrackerSize, halfTrackerSize);  
        }
       
        return Rectangle.Ceiling(rectF);
      }      
    }

    public override void Draw(Graphics graphics)
    {
      using (var pen = CreateBorderPen())
      using (var graphicsPath = CreateArrowPath())
      {
        graphics.DrawSmallShadow(graphicsPath, pen);
        graphics.DrawPath(pen, graphicsPath);      
        //graphics.DrawRectangle(Pens.Red, GetBoundingBox());//for debugging
      }
    }

    /// <summary>
    ///     Hit test.
    ///     Return value: -1 - no hit
    ///     0 - hit anywhere
    ///     > 1 - handle number
    /// </summary>
    public override int HitTest(Point point)
    {
      if (IsSelected)
      {
        for (int i = 1; i <= HandlesCount; i++)
        {
          if (GetHoverRectangle(i).Contains(point))
            return i;
        }
      }

      if (PointInObject(point))
        return 0;

      return -1;
    }

    public override bool IntersectsWith(Rectangle rectangle)
    {
      CreateRegionObjects();

      return AreaRegion.IsVisible(rectangle);
    }

    public override Cursor GetHandleCursor(int handleNumber)
    {
      switch (handleNumber)
      {
        case ArrowVertexType.Start:
        case ArrowVertexType.Middle:
        case ArrowVertexType.End:
          return AppCursors.SizeNESW;
        default:
          return AppCursors.FigureOverCursor;
      }
    }

    public override void MoveHandleTo(Point point, int handleNumber, bool shiftPressed)
    {
      switch (handleNumber)
      {
        case ArrowVertexType.Start:
          _startPoint = shiftPressed ? AdjustPointTo(point, _endPoint) : point;
          break;
        case ArrowVertexType.Middle:
          _middlePoint = point;
          _isMiddlePointSet = true;
          break;
        default:
          _endPoint = shiftPressed ? AdjustPointTo(point, _startPoint) : point;
          break;
      }

      if (shiftPressed && handleNumber != ArrowVertexType.Middle)
      {
        _isMiddlePointSet = false;
      }

      if (!_isMiddlePointSet)
        _middlePoint = MidPoint(_startPoint, _endPoint);

      ReleasePathRegion();
    }

    public override void Move(int deltaX, int deltaY)
    {
      _startPoint.X += deltaX;
      _startPoint.Y += deltaY;

      _middlePoint.X += deltaX;
      _middlePoint.Y += deltaY;

      _endPoint.X += deltaX;
      _endPoint.Y += deltaY;

      ReleasePathRegion();
    }

    /// <summary>
    ///   Create graphic objects used from hit test.
    /// </summary>
    private void CreateRegionObjects()
    {
      int requiredWidth = BorderWidth < SelectWidthRegion ? SelectWidthRegion : BorderWidth;

      if (_regionWidth != requiredWidth && AreaPath != null)
      {
        ReleasePathRegion();
      }

      if (AreaPath != null)
        return;

      // Create path which contains wide line
      // for easy mouse selection
      AreaPath = CreateArrowPath();

      _regionWidth = requiredWidth;

      var boundsPen = new Pen(BorderColor, _regionWidth);
      AreaPen = boundsPen; //new Pen(Color.Black, HOT_SPOT_SIZE);//TODO: refactor
      AreaPath.Widen(AreaPen);

      _regionWidth = BorderWidth;

      // Create region from the path
      AreaRegion = new Region(AreaPath);
    }

    protected override CanvasObject CloneIntrenal()
    {
      var clone = new CanvasArrow
      {
        _endPoint = _endPoint,
        _isMiddlePointSet = _isMiddlePointSet,
        _middlePoint = _middlePoint,
        _startPoint = _startPoint,
        IsArrowAtStart = IsArrowAtStart,
        IsArrowAtEnd = IsArrowAtEnd
      };

      return clone;
    }

    protected override Pen CreateBorderPen()
    {
      var pen = base.CreateBorderPen();

      var bigArrow = new AdjustableArrowCap(5, 5);

      if (IsArrowAtStart)
        pen.CustomStartCap = bigArrow;

      if (IsArrowAtEnd)
        pen.CustomEndCap = bigArrow;

      return pen;
    }

    /// <summary>
    ///   Get handle point by 1-based number
    /// </summary>
    /// <param name="handleNumber"></param>
    /// <returns></returns>
    protected override Point GetHandle(int handleNumber)
    {
      switch (handleNumber)
      {
        case ArrowVertexType.Start:
          return _startPoint;
        case ArrowVertexType.Middle:
          return _middlePoint;
        default:
          return _endPoint;
      }
    }

    protected override bool PointInObject(Point point)
    {
      CreateRegionObjects();

      return AreaRegion.IsVisible(point);
    }

    /// <summary>
    /// ReleasePathRegion.
    /// When object is invalidated, path used for hit test
    /// is released and should be created again.
    /// </summary>
    private void ReleasePathRegion()
    {
      if (AreaPath != null)
      {
        AreaPath.Dispose();
        AreaPath = null;
      }

      if (AreaPen != null)
      {
        AreaPen.Dispose();
        AreaPen = null;
      }

      if (AreaRegion != null)
      {
        AreaRegion.Dispose();
        AreaRegion = null;
      }
    }

    private static Point MidPoint(Point controlPoint1, Point controlPoint2)
    {
      return new Point((controlPoint1.X + controlPoint2.X) / 2, (controlPoint1.Y + controlPoint2.Y) / 2);
    }

    private Point AdjustPointTo(Point point, Point stillPoint)
    {
      if (Math.Abs(point.X - stillPoint.X) >= Math.Abs(point.Y - stillPoint.Y))
      {
        return new Point(point.X, stillPoint.Y);
      }

      return new Point(stillPoint.X, point.Y);
    }

    private GraphicsPath CreateArrowPath()
    {
      var graphicsPath = new GraphicsPath();

      Point[] points = { _startPoint, _middlePoint, _endPoint };
      graphicsPath.AddCurve(points, Tension);
      
      return graphicsPath;
    }
  }
}